/**
 * @file util/index
 * @author fanxueliang@qq.com
 */
import {trim, isNil, isEmpty, each, isString, keys, escapeRegExp, ceil, sum, uniq, includes} from 'lodash-es';
import type {SpecialCharsMap, SpecialCharsKey} from '../interface';

// 平均字符宽度占比 (相同字号前提下：对应字符宽度/一个中文的字符宽度)
// 这里忽略字重 和 字符间距的影响
// 调研文档 https://ku.baidu-int.com/knowledge/HFVrC7hq1Q/pKzJfZczuc/jaEQAh3gYK/QFqWmvubitKZac
const specialCharWidthMap: SpecialCharsMap = {
    ' ': 0.5568,
    '!': 0.2785,
    '"': 0.3556,
    '#': 0.5568,
    '$': 0.5568,
    '%': 0.8895,
    '&': 0.6674,
    "'": 0.1914,
    '(': 0.3335,
    ')': 0.3335,
    '*': 0.3895,
    '+': 0.5844,
    ',': 0.2785,
    '-': 0.3335,
    '.': 0.2785,
    '/': 0.2785,
    '0': 0.5568,
    '1': 0.5568,
    '2': 0.5568,
    '3': 0.5568,
    '4': 0.5568,
    '5': 0.5568,
    '6': 0.5568,
    '7': 0.5568,
    '8': 0.5568,
    '9': 0.5568,
    ':': 0.2785,
    ';': 0.2785,
    '<': 0.5844,
    '=': 0.5844,
    '>': 0.5844,
    '?': 0.5568,
    '@': 1.0156,
    'A': 0.6674,
    'B': 0.6674,
    'C': 0.7226,
    'D': 0.7226,
    'E': 0.6674,
    'F': 0.6116,
    'G': 0.7785,
    'H': 0.7226,
    'I': 0.2785,
    'J': 0.5,
    'K': 0.6674,
    'L': 0.5568,
    'M': 0.8335,
    'N': 0.7226,
    'O': 0.7785,
    'P': 0.6674,
    'Q': 0.7785,
    'R': 0.7226,
    'S': 0.6674,
    'T': 0.6116,
    'U': 0.7226,
    'V': 0.6674,
    'W': 0.9443,
    'X': 0.6674,
    'Y': 0.6674,
    'Z': 0.6116,
    '[': 0.2785,
    '\\': 0.2785,
    ']': 0.2785,
    '^': 0.4698,
    '_': 0.5568,
    '`': 0.3335,
    'a': 0.5568,
    'b': 0.5568,
    'c': 0.5,
    'd': 0.5568,
    'e': 0.5568,
    'f': 0.2785,
    'g': 0.5568,
    'h': 0.5568,
    'i': 0.2226,
    'j': 0.2226,
    'k': 0.5,
    'l': 0.2226,
    'm': 0.8335,
    'n': 0.5568,
    'o': 0.5568,
    'p': 0.5568,
    'q': 0.5568,
    'r': 0.3335,
    's': 0.5,
    't': 0.2785,
    'u': 0.5568,
    'v': 0.5,
    'w': 0.7226,
    'x': 0.5,
    'y': 0.5,
    'z': 0.5,
    '{': 0.3344,
    '|': 0.2603,
    '}': 0.3344,
    '~': 0.5844
};

const specialChars: string[] = keys(specialCharWidthMap);

/**
 * 计算字符串字符数
 * @param {string} source 原字符串
 * @returns {number}
 */
export const getLength = function (source: string): number {
    return String(source).length;
};

/**
 * 计算字符串字节数，一个中文算2个
 * @param {string} source 原字符串
 * @returns {number}
 */
export const getByteLength = function (source: string): number {
    if (!source || !isString(source)) {
        return 0;
    }
    // 处理连续的空格，因为多个连续的空格在html里也只会输出一个空格
    source = source.replace(/[ ]+/g, ' ');
    const charLengthArr: number[] = [];
    // 创建一个匹配所有特殊字符的正则表达式
    const specialCharsReg = new RegExp(`[${escapeRegExp(specialChars.join(''))}]`, 'g');
    // 返回被配置的特殊字符
    const results = uniq(source.match(specialCharsReg));
    // 按照出现的特殊字符进行遍历
    each(results, (char) => {
        const charkey = char as SpecialCharsKey;
        // 计算替换前的字符长度
        const sourceLength = getLength(source);
        // 用这个特殊字符生成正则，全局匹配
        const reg = new RegExp(escapeRegExp(charkey), 'g');
        // 将字符串中的这个特殊字符剔除（循环结束时所有出现的特殊字符都被剔除掉了）
        source = source.replace(reg, '');
        // 计算剔除这个特殊字符后的长度
        const currentLength = getLength(source);
        // 两次长度差 即是这个特殊字符的个数
        const charTimes = sourceLength - currentLength;
        // 获取这个特殊字符相对一个中文的宽度
        const charWidth = specialCharWidthMap[charkey];
        if (charWidth) {
            // 这个特殊字符在原字符串的总长度
            charLengthArr.push(charTimes * charWidth);
        }
    });
    // 将多余的空白字符(不包含空格，因为空格已经在特殊字符里被处理了)剔除
    source = source.replace(/\s/g, '');
    // 特殊字符字节长度（因字符宽度是相对于中文的宽度，一个中文=2个字节）+ 非特殊字符字节长度
    return ceil(sum(charLengthArr)) * 2 + getLength(source) * 2;
};

/**
 * 获取文本
 * @param {string} html 原字符串
 * @returns {string}
 */
export const getText = function (html: string): string {
    return decodeHTML(html.replace(/<[^>]+>/g, '')).replace(/&nbsp;/g, ' ');
};

/**
 * 计算文本字节数，一个中文算2个
 * @param {string} html 原字符串
 * @returns {number}
 */
export const getTextByteLength = function (html: string): number {
    html = getText(html);
    return getByteLength(html);
};

/**
 * 字符串格式替换，将字符串中'{0}{1}...'替换成对应参数
 * @param {string} source 原字符串
 * @param {Array} args 替换参数
 */
export const stringFormat = function (source: string, ...args: (number | string)[]): string {
    const len = args.length;
    return source.replace(/\{(\d+)\}/g, function (match, index) {
        index = parseInt(index, 10);
        return index >= len ? match : `${args[index]}`;
    });
};

export const isAllTypeEmpty = function (inputValue: any): boolean {
    const valueType = typeof inputValue;
    return isNil(inputValue) || (valueType === 'object' && isEmpty(inputValue)) || !trim(String(inputValue));
};

export const encodeHTML = function (source: string): string {
    return source
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;');
};

export const decodeHTML = function (source: string): string {
    return source
        .replace(/&quot;/g, '"')
        .replace(/&lt;/g, '<')
        .replace(/&gt;/g, '>')
        .replace(/&amp;/g, '&')
        .replace(/&#([\d]+);/g, function (match, index) {
            return String.fromCharCode(parseInt(index, 10));
        });
};

type UrlPart = 'protocol' | 'auth' | 'hostname' | 'port' | 'path' | 'querystring' | 'hash';

export const validateUrl = function (url: string): boolean | string {
    let startIndex = 0;
    const len = url.length;

    if (!isString(url)) {
        return '链接地址格式错误';
    }

    const urlObj = {
        protocol: '',
        auth: '',
        hostname: '',
        port: '',
        path: '',
        querystring: '',
        hash: ''
    };

    const regMap = {
        protocol: /^(https?):\/{2}?/i,
        auth: /^(\w+(?::\w*)?)@/i,
        hostname: /^(localhost)|^(\w(?:[\w-]*\w)?(?:\.\w(?:[\w-]*\w)?)+)/i,
        port: /^(?::(\d{2,5}))?/i,
        path: /^(\/[^?#"<>\u4e00-\u9fa5\s]*)?/i,
        querystring: /^(?:(\?[^#"<>\u4e00-\u9fa5\s]*))?/i,
        hash: /^(?:#([^"<>\u4e00-\u9fa5\s]*))?/i
    };
    each(regMap, function (regex: RegExp, key: string) {
        const matchArr = url.substring(startIndex, len).match(regex);
        if (matchArr && matchArr[0]) {
            urlObj[key as UrlPart] = matchArr[0];
            startIndex += matchArr[0].length;
        }
    });

    // 解析完成后，长度应该和总长度一致，否则说明有非法字符
    if (startIndex !== len) {
        return '链接地址包含非法字符或中文字符';
    }
    // 解析完成后，必须有 protocol 和 hostname
    if (!urlObj.protocol) {
        return '链接地址必须以http://或https://开头';
    }
    if (!urlObj.hostname) {
        return '域名不能为空';
    }
    return true;
};

export function parseDefaultValue(specDefaultValue: any) {
    let defaultValue;
    if (!specDefaultValue) {
        return undefined;
    }
    try {
        // lego会强制将组件spec中各个类型的defaultValue转成字符串
        // 无奈，这里转一下
        defaultValue = specDefaultValue && isString(specDefaultValue) ? JSON.parse(specDefaultValue) : specDefaultValue;
    } catch (e) {
        defaultValue = specDefaultValue;
    }
    return defaultValue;
}

export function validateAppScheme(text: string): boolean {
    const schemeReg = /^([a-z]+[a-z0-9-.+_]*):\/\//i;
    const inputSchemeResults = schemeReg.exec(text);
    if (inputSchemeResults && inputSchemeResults[1]) {
        const errorSchema: string[] = ['http', 'https', 'scheme'];
        return !includes(errorSchema, inputSchemeResults[1]);
    }
    return false;
}
